package com.im.service.exception;

import com.im.common.exception.BusinessException;
import com.im.common.model.RestResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 统一异常处理类,在@RequestMapping上所有的异常都会被捕获。
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    @Order(4)
    public RestResponse handleException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        if (e instanceof BusinessException) {
            BusinessException ex = (BusinessException) e;
            return new RestResponse(false, ex.getCode(), ex.getMsg());
        } else if (e instanceof NoHandlerFoundException) {
            return new RestResponse(false, 404, "找不到资源", request.getServletPath());
        } else if (e instanceof HttpRequestMethodNotSupportedException) {
            String method = ((HttpRequestMethodNotSupportedException) e).getMethod();
            return new RestResponse(false, 405, "不支持 " + method + " 请求方法");
        } else if (e instanceof HttpMediaTypeNotSupportedException) {
            return new RestResponse(false, 415, "不支持媒体类型", request.getServletPath());
        } else if (e instanceof MethodArgumentNotValidException) {
            return new RestResponse(false, 416, ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(), request.getServletPath());
        } else if (e instanceof SQLException) {
            log.error(e.getMessage(), e);
            return new RestResponse(false, 417, "sql语法错误");
        }
        log.error(e.getMessage(), e);
        return RestResponse.error("服务器内部错误");
    }

    // <1> 处理 form data方式调用接口校验失败抛出的异常
    @ExceptionHandler(BindException.class)
    @Order(3)
    public RestResponse bindExceptionHandler(HttpServletRequest request, BindException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> collect = fieldErrors.stream()
                .map(o -> o.getDefaultMessage())
                .collect(Collectors.toList());
        return new RestResponse(false, HttpStatus.BAD_REQUEST.value(), "客户端请求参数错误", collect);
    }

    // <2> 处理 json 请求体调用接口校验失败抛出的异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @Order(2)
    public RestResponse methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        List<String> collect = fieldErrors.stream()
                .map(o -> o.getDefaultMessage())
                .collect(Collectors.toList());
        return new RestResponse(false, HttpStatus.BAD_REQUEST.value(), "客户端请求参数错误", collect);
    }

    // <3> 处理单个参数校验失败抛出的异常
    @Order(1)
    @ExceptionHandler(ConstraintViolationException.class)
    public RestResponse constraintViolationExceptionHandler(HttpServletRequest request, ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        List<String> collect = constraintViolations.stream()
                .map(o -> o.getMessage())
                .collect(Collectors.toList());
        return new RestResponse(false, HttpStatus.BAD_REQUEST.value(), "客户端请求参数错误", collect);
    }

}